<?php
require_once "Site.class.php";
require_once "Audit.lib.php";
/*
 * 2010/11/30: added get_user_permission()
 * 2010/12/03: fix bug in user_can()
 * 2010/12/16: rename app_group to auth_group
 * 2010/12/17: support captcha
 * 2010/12/20: account suspend after 5 time reties
 *             add last_change password, next_change_password, password_retry_count
 *             alter table cms_user add column next_change_password date not null after last_change_password;
 *             alter table cms_user add column password_retry_count tinyint unsigned not null default 0 after email;
 *             alter table cms_user add column account_lock_until datetime not null after last_change_password;
 *             test case: test lock accout until field
 *                        test update password flow 
 *
 *  2011/05/10
 *  MySQL
 *    alter table cms_user add column reset_password_request_time datetime default '0000-00-00'  after  next_change_password; 
 *  Files
 *  tmpl/cms/login.html
 *  tmpl/cms/login/*.html
 *  lib/Audit.class.php
 *  site_lib/CMS_Login.class.php
 *  site_lib/CMS_LoginCookie.class.php 
 *  Crontab 
 *    # cleanup session file of online website
 *    14 5   * * *    root    find /home/online/temp/session/ -mmin +3600 -exec rm -f  '{}' \;
 *
 *  fix on cms_auth_permission table: (drop app_group column)
 *    update cms_auth_permission set auth_group = app_group;
 *    alter table cms_auth_permission drop column app_group;
 *
 * 2011/05/18
 *   add get_cms_login(), get_navigation_by_login()
 *   MySQL
 *    alter table cms_user add column navigation char(64) not null  after  reset_password_request_time; 
 * 
 *	2011/11/28
 *		-update_password, preventing selected invalid user acccount (status=0)
 *		-get_login_debug
 *		-update_password
 *		-get_cms_user_email
 *		-update_cms_password
 *		-set_reset_password_request_time
 *		-get_reset_password_request_time
 */

// to be replace Channel.pm
// WWW-Authenticate: Digest realm=...
// http://php.net/manual/en/features.http-auth.php

require_once('CMS_LoginCookie.class.php');

class CMS_Login extends Site{


  function cgiapp_prerun($rm=''){
    if (method_exists($this, 'no_login') && $this->no_login() == 1){
      return;
    }
    if (method_exists($this, 'no_login_with_runmode') && $this->no_login_with_runmode($rm) == 1){
      return;
    }

    $login_cookie = new CMS_LoginCookie();
    $login_cookie->check();

    $logged_in = $this->s_param('~CMS_logged-in');
    
#   if ($logged_in == 1){
#     $this->session->expire('~logged-in', $this->param('short_expire_time'));
#     $this->session->expire('memberID', $this->param('short_expire_time'));  
#   }
   
    if ($rm != 'login' && $logged_in != 1){       
      $landing = $this->param('INDEX');
      if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'GET'){
        $landing = $_SERVER['REQUEST_URI'];
      }
      
      if ($this->debug){ echo "RM: $rm, URI ", $_SERVER['REQUEST_URI'],"<BR/>\n";}
      if ($this->debug){ echo "RM: $rm, Final landing: $landing<BR/>\n"; }
  
      $this->param('.done', $landing);
      $this->redirect('/cms/builder/login.php?_done='.$landing);
      
    } elseif ($rm == 'login'){
      $landing = $this->get_q('_done');
      if ($this->debug){ echo "RM=Login, landing to:$landing<BR/>\n"; }
      $this->param('_done', $landing);
    }
  } # cgiapp_prerun;

###################### COOKIE HANDLE START ##################################################



################### login & logout ##########################
  
  function showlogin($status=''){
    $is_popup = $this->param('is_popup');
    
    $tmpl_path = 'cms/builder/login.html';
    if ($is_popup == 1 || $this->get_q('popup')==1){
      $tmpl_path = 'cms/builder/login_popup.html' ;
    }
    
    if ($this->debug){ echo "Function: showlogin: Status Msg: $status, URI:  ".$_SERVER['REQUEST_URI'].", using tmpl: $tmpl_path<BR/>\n";}
      
    if ($status != ''){ 
      $this->tmpl_assign('login',    $this->get_q('login'));
      $this->tmpl_assign('landing',  $this->param('landing'));
      $this->tmpl_assign('cms_name', $this->param('SITE'));
      $this->tmpl_assign('status',   $status);
      return $this->load_tmpl($tmpl_path);
    }
  
    $this->tmpl_assign('landing',  $this->param('landing'));
    $this->tmpl_assign('login',    $this->get_q('login'));
    $this->tmpl_assign('cms_name', $this->param('SITE'));
    return $this->load_tmpl(  $tmpl_path);
  }
  
  # verify 
  # 
  function verify_captcha($captcha_in=null){
    if ($captcha_in == null){ # not pass by argument, get from form
      $captcha_in = $this->get_q('captcha');
         
    }
    
    $captcha_ok = 0;
    
    $captcha_in_session = $this->get_param('s', 'Captcha');

    if ($captcha_in == $captcha_in_session && 
        $captcha_in_session != '' && 
        $captcha_in_session != '#CAPTCHA_RESETED#'){
      $captcha_ok=1;
    }
    # reset captcha 
    $this->s_param('Captcha', '#CAPTCHA_RESETED#');

    return $captcha_ok;
  }

  # verify cms login passwd
  function login(){   
    $login_ok   = 0;
    $captcha_ok = $this->verify_captcha();
    
    $error_message = '';
    if ($captcha_ok == 0){  
      $error_message = "驗証碼錯誤"; #Incorrect captcha ($captcha_in vs $captcha_in_session).";
    }
  
    if ($captcha_ok==1){
      $login    = $this->get_q('login',    '', 'string');
      $password = $this->get_q('password', '', 'string');
    
      $dbh_r = $this->db_connect('read');
      $cms_user_table = $this->get_cms_tablename('cms_user');
      
      list($mid, $password_hash, $salt, $username, $account_lock_until, $now_timestamp) = db_selectrow_array($dbh_r, "select id, password, salt, name, unix_timestamp(account_lock_until), unix_timestamp(NOW()) from $cms_user_table where login=? and status=1", $login);
      if ($account_lock_until > $now_timestamp){
        $account_lock_until = db_select_one($dbh_r, "select account_lock_until from $cms_user_table where id=?", $mid);
        return $this->errmsg('Account is temporarily locked until '.$account_lock_until);
      }

      $password_hash_in = $this->gen_password_hash($password, $salt);
    
      $login_ok = 0;
      if($password_hash_in == $password_hash || $password == $password_hash){ # simple implementation first
        if ($this->debug){ echo("user $login auth OK<BR/>"); }
        #session_regenerate_id
        $login_ok = 1;
        
        $dbh_w = $this->db_connect('write');
        db_do($dbh_w, "update $cms_user_table set password_retry_count=0 where id=?", $mid);
        
        # check next_chagne password 
        list($next_change_password, $current_timestamp) = db_selectrow_array($dbh_r, "select unix_timestamp(next_change_password), unix_timestamp(NOW()) from $cms_user_table where id=? ", $mid );
        if ($next_change_password < $current_timestamp){
          # request user to change password
          $this->header_type('redirect');
          #$this->header_props(array('url'=>'/cms/update_password.php?_done='.$this->param('_done').'&login='.$this->get_q('login')));
          $this->header_props(array('url'=>'/cms/builder/login.php?action=edit&_done='.$this->param('_done').'&login='.$this->get_q('login')));
          return;         
        }
        
      } else {
      
        $dbh_r = $this->db_connect('read');
        $fail_count = db_select_one($dbh_r, "select password_retry_count from $cms_user_table where id=?", $mid);
        $fail_count ++;

        $error_message = "Incorrect username or password. ($fail_count)";
        
        $dbh_w = $this->db_connect('write');
        
        if ($fail_count >= 5){
          #account will be suspended for n minute
          db_do($dbh_w, "update $cms_user_table set account_lock_until=date_add(NOW(), interval 5 minute) where id=?", $mid);
        } else {
          db_do($dbh_w, "update $cms_user_table set password_retry_count=? where id=?", array($fail_count,$mid));
        }
        if ($this->debug){ echo("user $login auth FAILED<BR/>");}
      }
    }
    
    # Login Success
    if ($login_ok == 1){    
      $this->set_login_session($mid, $login, $username);
      return $this->redirect_done_url();
    } else {
      return $this->showlogin(  $error_message );
    }
  
  }


  function set_login_session($mid, $login, $username){
    $ip = $_SERVER['REMOTE_ADDR'];
    
    $login_cookie = new CMS_LoginCookie();
    $h_user = array('id'=>$mid, 'login'=>$login, 'name'=>$username);

    return $login_cookie->set($h_user);  
    #$this->s_param('CMS_USERID',   $mid);
    #$this->s_param('CMS_LOGIN', $login);
    #$this->s_param('CMS_USERNAME', $username);
    #$this->s_param("~CMS_logged-in", 1);
  }


  # return to url specified in .done / _done parameter
  #
  function redirect_done_url(){
    $redirect_url = $this->param('_done');
    if (! isset($redirect_url)){
      $redirect_url = $this->get_q('_done');
      if (!isset($redirect_url)){
      $redirect_url = $this->param('INDEX');
      }
    }
 
    $this->header_type('redirect');
    $this->save_message_to_session(); 
    $this->header_props(array('url'=>$redirect_url));
    return;
  }
 
  
  function update_password(){
    $login  = $this->get_q('login', '', 'string');
    $password = $this->get_q('password', '', 'string');
    
    #if ($this->debug){ echo "Site.class login with (login: $login, $passwd) <BR/>";}
  
    $dbh_r = $this->db_connect('read');
    $cms_user_table = $this->get_cms_tablename('cms_user');
          
    # local password checking
//    list($mid, $password_hash, $salt, $username) = db_selectrow_array($dbh_r, "select id, password, salt, name from $cms_user_table where login=? ", $login );
    list($mid, $password_hash, $salt, $username) = db_selectrow_array($dbh_r, "select id, password, salt, name from $cms_user_table where login=? and status=1", $login );
    $password_hash_in = $this->gen_password_hash($password, $salt);
    
    $login_ok = 0;
    if($password_hash_in == $password_hash || $password == $password_hash ){ # simple implementation first
      $new_password1 = $this->get_q('new_password1', '', 'string');
      $new_password2 = $this->get_q('new_password2', '', 'string');
      
      if ($password == $new_password1){
      	if (method_exists($this, 'cms_login_redirect_to')){
      		return $this->cms_login_redirect_to('edit', '請輸入不同的密碼');
      	}
        return $this->errmsg('Please update a different password.');
      }
      
      if ($new_password1 == $new_password2){
        $this->update_cms_password($login, $new_password1);
        
        $this->set_login_session($mid, $login, $username);
        return $this->redirect_done_url();
      } else {
      	if (method_exists($this, 'cms_login_redirect_to')){
      		return $this->cms_login_redirect_to('edit', '兩次輸入新密碼不同');
      	}
        return $this->errmsg('New password mis-match.');
      }
      
    } else {
      if (method_exists($this, 'cms_login_redirect_to')){
    		return $this->cms_login_redirect_to('edit', '帳號或密碼不正確');
    	} 
      return $this->errmsg('Incorrect username / password.');
    }
    
    if (method_exists($this, 'cms_login_redirect_to')){
  		return $this->cms_login_redirect_to('edit', '更新密碼錯誤');
  	} 
    return $this->errmsg('Error in updating passowrd.');
  }
  
  function get_cms_user_email($login){
    $cms_user_table = $this->get_cms_tablename('cms_user');      
    $dbh_r = $this->db_connect('read');
    //$email = db_select_one($dbh_r, "select email from $cms_user_table where login=? ", $login );
	$email = db_select_one($dbh_r, "select email from $cms_user_table where login=? and status=1", $login );
    return $email;
  }

  function update_cms_password($login, $new_password){
    $cms_user_table = $this->get_cms_tablename('cms_user');      
    $dbh_r = $this->db_connect('read');
    
    //$mid = db_select_one($dbh_r, "select id from $cms_user_table where login=? ", $login );
	$mid = db_select_one($dbh_r, "select id from $cms_user_table where login=? and status=1", $login );

    $new_salt = $this->gen_salt();
    $new_password_hash = $this->gen_password_hash($new_password, $new_salt);

    $dbh_w = $this->db_connect('write');
    db_do($dbh_w, "update $cms_user_table set password=?, salt=? where id=?", array($new_password_hash, $new_salt, $mid));
    db_do($dbh_w, "update $cms_user_table set last_change_password=NOW(), next_change_password=date_add(NOW(), interval 3 month), reset_password_request_time=NOW() where id=?", array($mid));        
  }
  
  function get_reset_password_url($login){
    
    $reset_password_request_time = $this->set_reset_password_request_time($login);
    
    $ostr = $login.'##'.$reset_password_request_time;
    $pass_hash_key = $this->param('pass_hash_key');
    $auth_code = authcode($ostr,'ENCODE', $pass_hash_key);
    
    $auth_code = urlencode($auth_code);
          
    $url = $this->param('URL').'cms/builder/login.php?action=verify_forget_password&login='.$login.'&code='.$auth_code;
    return $url;
  }
  
  function verify_reset_password_code($login, $auth_code){
    $request_time = $this->get_reset_password_request_time($login);
    
    $now = time();
    if ($now > $request_time && ($now - $request_time < 3600) ){ 
      $pass_hash_key = $this->param('pass_hash_key');
      $decoded = authcode($auth_code,'DECODE', $pass_hash_key);
      if ($decoded == $login.'##'.$request_time){
        return 1;
      }
    }
    return 0;
  }

  function set_reset_password_request_time($login){
    $cms_user_table = $this->get_cms_tablename('cms_user');      
    $dbh_w = $this->db_connect('write');
    $time = time();
    //db_do($dbh_w, "update $cms_user_table set reset_password_request_time=from_unixtime(?) where login=?", array($time, $login));
	db_do($dbh_w, "update $cms_user_table set reset_password_request_time=from_unixtime(?) where login=? and status=1", array($time, $login));
    return $time;
   }

   function get_reset_password_request_time($login){
     $cms_user_table = $this->get_cms_tablename('cms_user');      
     $dbh_r = $this->db_connect('read');
     
     //$time = db_select_one($dbh_r, "select unix_timestamp(reset_password_request_time) from $cms_user_table where login=?", $login);
	 $time = db_select_one($dbh_r, "select unix_timestamp(reset_password_request_time) from $cms_user_table where login=? and status=1", $login);
     return $time;
   }

#password_retry_count: 0
#last_change_password: 2011-03-25
#  account_lock_until: 2010-12-20 14:05:45
#next_change_password: 2011-06-25  
  function get_login_debug($login){   
    $cms_user_table = $this->get_cms_tablename('cms_user');      
    $dbh_r = $this->db_connect('read');
/*    
    $h_row = db_selectrow_hash($dbh_r, "select password_retry_count,last_change_password, 
     account_lock_until,next_change_password, reset_password_request_time
     from $cms_user_table where login=? ", $login );
     */
    $h_row = db_selectrow_hash($dbh_r, "select password_retry_count,last_change_password, 
     account_lock_until,next_change_password, reset_password_request_time
     from $cms_user_table where login=? and status=1", $login );
	 
    $a_result = array();
    foreach ($h_row as $key => $value){
      array_push($a_result, "$key => $value");
    } 
    return implode("<BR/>", $a_result);
  }

  function gen_salt($length=8){
   $my_array = array("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",  "m", "n", "p", "0", "1", "2", "3", "4", "5",'6','7','8','9','q','r','s','t','u','v','w','x','y','z');
   $temp= array_rand(array_flip($my_array), $length); 
   return implode('', $temp);
    #return  'x1234sod';
  }

  function gen_password_hash($password, $salt){
    #return $password;
    # PSI Team suggest halt formula
    # Encrypt formula:  Hash digit = sha1(timestamp + randomnumber + adid + securekey) 
    # we can add 'addtime' & use sha1 when create the digest
    return md5($password.$salt);
  }

  function logout(){
    $referer = $_SERVER['HTTP_REFERER'];
    #$mid     = $this->s_param('mid');
    #if ($mid > 0){     
    #}  
  
  }
################### User permission ##########################



  function get_cms_username(){
    return $this->s_param('CMS_USERNAME');
  }
  
  function get_cms_login(){
    return $this->s_param('CMS_LOGIN');
  }
  
  function get_cms_userid(){
    return $this->s_param('CMS_USERID');
  }

  /* Get user's permission on this program
   * 
   *
   */
  function get_user_permission(){
    $cms_auth_group = $this->param('cms_auth_group'); # array or string
    $a_auth_group = array('*');
    if (! is_array($cms_auth_group)){
      array_push($a_auth_group, $cms_auth_group);
    } else {
    	foreach ($cms_auth_group as $temp){
        array_push($a_auth_group, $temp);
      }
    }

    $cms_userid = $this->get_cms_userid();
    
    $dbh_r = $this->db_connect('read', $this->param('DATABASE'));
    $cms_auth_permission_table  = $this->get_cms_tablename('cms_auth_permission');
    $cms_auth_action_type_table = $this->get_cms_tablename('cms_auth_action_type');
      
    $h_permitted_action = array();
    $permission_count = 0;
    foreach ($a_auth_group as $cms_auth_group){
      $action_permission = db_select_one($dbh_r, "select action_permission from $cms_auth_permission_table where cms_user=? and auth_group=?", array($cms_userid, $cms_auth_group));
      $a_action_permission = explode(',', $action_permission);
      foreach ($a_action_permission as $temp){
        $h_permitted_action[$temp] = $cms_auth_group; 
        $permission_count++;
      }
    }
    
    $a_result = array();
    if ($permission_count > 0){
      $h_action = db_selectcol_hash($dbh_r, "select id,name from $cms_auth_action_type_table where status=1");
      foreach ($h_permitted_action as $key => $value){
        if ($key > 0){
          array_push($a_result, $h_action[$key]."($value)");
        }
      }
      return $this->get_cms_username().':'.implode(',', $a_result);
    }
    return '';
  }

  // can user have permission for $action on this program (based on auth_group)
  #  user      auth_group    action                   
  #  cliff     poi          [x]list [x]edit [x]del  
  #  freelance poi          <none selected>  
  #
  #
  function user_can($action, $silent=0){
    #return 1; 
    $cms_auth_group = $this->param('cms_auth_group'); # array or string
    $a_cms_auth_group = array();
    if (! is_array($cms_auth_group) ){
      $a_cms_auth_group = array($cms_auth_group);
    } else {
    	$a_cms_auth_group = $cms_auth_group;
    }
    
    $cms_userid = $this->get_cms_userid();
    
    if (empty_string($cms_userid)){   
      return $this->permission_error("Please change CMS permssion setting");
    }

    $permitted = 0;
    $dbh_r = $this->db_connect('read', $this->param('DATABASE'));
    $cms_auth_action_type_table = $this->get_cms_tablename('cms_auth_action_type');      

    $cms_action = db_select_one($dbh_r, "select id from $cms_auth_action_type_table where name=?", $action);
    if ($cms_userid > 0 && $cms_action > 0){
      if ($this->is_user_permitted($dbh_r, $cms_userid, '*',        $cms_action) ){
      	$permitted = 1;
      }
      foreach ($a_cms_auth_group as $cms_auth_group){
        if ($permitted == 0 && $this->is_user_permitted($dbh_r, $cms_userid, $cms_auth_group, $cms_action) ){
          #$this->s_param('cms_user', $cms_userid);   
          $permitted = 1;
        }
      }
    }
    
    if (! $permitted ){
      if ($silent==1) return 0;
      # show html popup
      $login = $this->get_cms_username();
      return $this->permission_error("User $login has no permission! ($action)");
    }
    return 1;
  }
  
  function get_cms_tablename($name){
    $DATABASE = $this->param('DATABASE');
    switch($name){
      case 'cms_auth_permission' : return $DATABASE.'.cms_auth_permission';
      case 'cms_user'            : return $DATABASE.'.cms_user';
      case 'cms_auth_action_type': return $DATABASE.'.cms_auth_action_type';
    }
    
    return '';
  }
  
  /* check a very specific permission
   * @return: 0 : user is not permitted
   *          1 : permitted
   */
  function is_user_permitted($dbh_r, $cms_userid, $cms_auth_group, $cms_action){
    
    $cms_auth_permission_table = $this->get_cms_tablename('cms_auth_permission');
    
    $action_permission = db_select_one($dbh_r, "select action_permission from $cms_auth_permission_table where cms_user=? and auth_group=?", array($cms_userid, $cms_auth_group));
    
    if (empty_string($action_permission)) return 0;
    
    $a_action_permission = explode(',', $action_permission);
    return in_array($cms_action, $a_action_permission) ? 1: 0;
  }
  
 
  /* always return 0 to show this action is not permitted.
   */
  function permission_error($message){
    $this->message($message);
    return 0;
  }
 
 ################### Auditing ##########################
 
  function audit_log($action, $msg){

    $user    = $this->get_cms_username();
    $user_id = $this->get_cms_userid();
    $app     = $this->param('this_cgi');
    audit_log('cms', $user_id."[$user]\t$app\t$action\t$msg");
  }

  function audit($type, $group, $message){
    $log_name = isset($this->log_name) ? $this->log_name : get_class(); 
    $prefix   = $type.'_'.$log_name;
    audit_log($group, $prefix.': '.$message);
  }

  # 
  function get_navigation_by_login(){
  	$www_root  = $this->param('WWW');
    $login = $this->get_cms_login();
    
    if ($login != ''){
      # only find in CMS root directory
      $default_individual_filename = 'cms/navigation/login/'.$login.'.html';
    }
    
    if (file_exists($www_root.$default_individual_filename)){
      return $www_root.$default_individual_filename;
    }

    $cms_login_id = $this->get_cms_userid();
    
    $cms_user_table = $this->get_cms_tablename('cms_user');      
    $dbh_r = $this->db_connect('read');
    
    $h_row = db_selectrow_hash($dbh_r, 'select * from '.$cms_user_table.' where id=?', $cms_login_id);
    if (isset($h_row['navigation'])){
    	$custom_filename = 'cms/navigation/'.$h_row['navigation'].'.html';
    	if (file_exists($www_root.$custom_filename)){
        return $www_root.$custom_filename;
      }
    }
    
  	return '';
  }  
	 function cms_site_name(){
	 	  if ($this->param('SITE_NAME') != ''){
	 	  	return $this->param('SITE_NAME');
	 	  }
	 	  
	 	  return $this->param('SITE');
	 }
  /*************************************************************************/
  /* Custom function for website start here                                */
  /*************************************************************************/  
}

?>
